// PopCulture - A package to compute metrics on Wikipedia articles.
// Copyright (C) 2012  Nuno J. Silva
//
// This file is part of PopCulture
//
// PopCulture is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// PopCulture is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with PopCulture.  If not, see <http://www.gnu.org/licenses/>.


// This file contains the main code for the PopCulture d3.js-based
// visualization, along with the accompanying stylesheet and HTML
// files.


rebuilt = 0;

// http://stackoverflow.com/questions/3873595/how-to-disable-firefoxs-default-drag-and-drop-on-all-images-behavior-with-jquer
$(document).bind("dragstart", function() {
     return false;
});


// http://stackoverflow.com/questions/3375056/disable-text-selection-in-chrome
document.onselectstart = function(){return false;}

// Set to 0 to allow networkless operation.
autocomplete_mode = 1;

// Configuration: enter here the cherrypy instance URL.
local_server = 'http://localhost:8080';

// Initial width for content pane
content_pane_width = 250;

mirror_mode = false;

duo_normalization_mode = false;

timeline_drag_start = null;
timeline_drag_stop = null;

/* Initially inspired by http://mbostock.github.com/d3/ex/stack.html,
 * but now I doubt there is any resemblance. */

vertical_scale_upper = d3.scale.linear();
vertical_scale_lower = d3.scale.linear();
horizontal_scale = d3.scale.linear();
x = function(d) { return horizontal_scale(d[5])};

function binsearch (list, datum) {
    var start = 0;
    var end = list.length;
    do {
	mid = (start + end) >> 1;
	if (list[mid][5] < datum) {
	    start = mid + 1;
	} else {
	    end = mid + 1;
	}
    } while ((end - start) > 10);

    for (var i = start; (i < end) && (list[i][5] < datum); i++) {
	;
    }

    if (i == end) {
	i--;
    }

    return i;
}

function update_height() {
    vis_height = $(document).height() - 20;
    $("#plots").height(vis_height);
    $("#content").height(vis_height - 50);
    $("#sidebartoggle").css('top', vis_height/2);
    $("#sidebartogglebar").css("height", "100%");

    plot_height = vis_height / 2 - 50;

    d3.select("#lower").attr("transform", "translate(0," + (plot_height + 50) + ")");
    d3.select('#invisible_rect').attr("transform", "translate(0," + plot_height + ")");

    vertical_scale_upper.range([plot_height, 0]);
    vertical_scale_lower.range([0, plot_height]);
}

function update_width() {
    vis_width = $(document).width() - 190 - $("#rightpane").width();
    $("#rightpane").css("left", "");
    $("#rightpane").css("right", "0");
    $("#plots").width(vis_width);
    horizontal_scale.range([0, vis_width]);
}

function update_geometry_and_plots() {
    update_height();
    update_width();
    update_plots();
}

// http://stackoverflow.com/questions/599288/cross-browser-window-resize-event-javascript-jquery
function run_with_delay(lambda) {
    var timer;
    return function() {
	clearTimeout(timer);
	timer = setTimeout(lambda, 200);
    }
}

function run_with_vardelay(lambda, delay) {
    var timer;
    return function() {
	clearTimeout(timer);
	timer = setTimeout(lambda, delay);
    }
}

$(window).resize(run_with_delay(update_geometry_and_plots, 100));

update_width();
update_height();

sites = new Array();
global_data = new Object();
global_data['upper'] = new Array();
global_data['lower'] = new Array();

var servedreqs = new Array();

gradient_color_scale = d3.scale.linear().domain([0,1]).range(["orange", "blue"]);
gradient_highlight_color_scale = d3.scale.linear().domain([0,1]).range(["yellow", "darkgreen"]);
gradient_stop_color_accessor_log = function(data_index, color_scale){return function(datum){return color_scale(Math.log(999 * datum[1] + 1)/6.908);};};
gradient_stop_color_accessor_normal = function(data_index, color_scale){return function(datum){return color_scale(datum[1]);};};
gradient_stop_color_accessor = null;

gradient_counter = 0
selection = null
start_date = null
end_date = null
current_plot = null;
current_highlighted_plot = null;
current_gradient_fill = null;
current_highlighted_gradient_fill = null;

plots_svg_container = d3.select("#plots_svg_container");

function create_selection(x,y) {
    click_mode = 1;
    tooltip.style("visibility", "hidden");
    click_start_px = x;
    click_end_px = x;
    selection = plots_svg_container.append("svg:rect");

    selection
	.attr("class", "selectionarea")
	.attr("x", x)
	.attr("y", 0)
	.attr("fill", "blue")
	.attr("opacity", "0.4")
	.attr("width", 0)
	.attr("height", vis_height)
	.on('contextmenu', function (d, i) { 
	    select_mode = 0;
	    destroy_selection();
	})
	.on('mousemove',function(){
	    update_selection(d3.mouse(this)[0],
			     d3.mouse(this)[1]);
	})
	.on("mouseup", 
	    function () {
		select_mode = 0;
		destroy_selection();
		if (click_start_px != click_end_px) {
		    focus_on_range("#upper", click_start_px, click_end_px);
		} else {
		    tooltip.style("visibility", "visible");
		}
	    });
}


function update_selection(x, y) {
    click_end_px = x;

    if (click_start_px < click_end_px) {
	selection.attr("x", click_start_px);
	selection.attr("width", click_end_px - click_start_px);
    } else {
	selection.attr("x", click_end_px);
	selection.attr("width", click_start_px - click_end_px);
    }
}


function destroy_selection() {
    select_mode = 0;
    click_mode = 0;
    selection.remove();
    selection = null;
}

function focus_on_range(plot, px_start, px_end) {
    console.log('focus_on_range');
    /* Get timestamps from the pixel values */
    range_start = horizontal_scale.invert(px_start);
    range_end = horizontal_scale.invert(px_end);

    /* Order timestamps by chronological order */
    if (range_end < range_start) {
	/* Could be done with a single statement, [a,b] = [b,a], but
	 * V8 does not support it. */
	tmp = range_start;
	range_start = range_end;
	range_end = tmp;
    }

    undo_store.push([start_date, end_date]);
    replot_range(range_start, range_end);
}

function replot_range(start_timestamp, end_timestamp){
    /* Update the selection in the left menu */
    update_date_divs(new Date(start_timestamp * 1000),
		     new Date(end_timestamp * 1000));

    /* Replot the plots using the new range */
    update_plots();
}

function generate_author_color_acessor(authors_list, color_scale, rest) {
    var lambda = function(datum){
	if (authors_list.indexOf(datum[1]) != -1){
	    return color_scale(datum[1]);
	} else {
	    return rest;
	}};
    return lambda;
}

function generate_gradient(visualization, data, data_index, authors_list, section_id, stop_color_accessor, maxlen, color_scale){
    var gradient_id = "lg" + visualization.attr('id') +section_id;
    // It should be noted that performace is actually greater if a new
    // defs is added for each gradient.
    var gradient = visualization
	.append("svg:defs")
	.append("svg:linearGradient")
	.attr("id", gradient_id);

    if (data_index === 3) {
	var new_scale = color_scale.copy().domain([0, maxlen]);
	
	stop_color_accessor = function (datum){return new_scale(datum[1]);}
    }

    if (data_index == 4) {
	data_index = 14;
	var i;
	for (i = 0; i < data.length; i++) {
	    if (authors_list.indexOf(data[i][8]) == -1){
		data[i][14] = null;
	    } else {
		data[i][14] = data[i][8];
	    }
	}
    }

    var i, j = 0;
    var new_data = new Array();
    var visid = visualization.attr('id');
    new_data.push([data[0][5], data[0][data_index]]);
    minimum = horizontal_scale.invert(2.5) - horizontal_scale.invert(1);
    for (i = 1; i < data.length - 1; i++) {
	if (data[i][5] - new_data[j][0] < minimum) {
	    new_data[j][1] = Math.max(new_data[j][1], data[i][data_index]);
	    continue;
	}
	if ((data[i-1][data_index] == data[i][data_index])
	    && (data[i][data_index] == data[i+1][data_index])) {
	    continue;
	}
	new_data.push([data[i][5],data[i][data_index]]);
	j++;
    }
    if (i < data.length) {
	new_data.push([data[i][5], data[i][data_index]]);
    }

    var plot_start = data[0][5];
    var plot_end = data[data.length - 1][5];
    var factor = 100 / (plot_end - plot_start);

    /* cleanup */
    data = null;

    var offset_accessor = function(datum){return ((datum[0] - plot_start) * factor) + '%';};

    gradient.selectAll("stop")
	.data(new_data)
	.enter()
	.append("svg:stop")
	.attr("offset", offset_accessor)
     	.attr("stop-color", stop_color_accessor);

    return gradient_id;
}

tooltip_static = 0;
select_mode = 0;
click_mode = 0;
click_start_px = 0;
click_end_px = 0;

wait_divs = new Array();
wait_divs['#upper'] = d3.select("#dwaitplot1");
wait_divs['#lower'] = d3.select("#dwaitplot2");
progress_spans = new Array();
progress_spans['#upper'] = d3.select("#dwaitplot1_progress");
progress_spans['#lower'] = d3.select("#dwaitplot2_progress");
progress_urls = new Array();
progress_urls['#upper'] = null;
progress_urls['#lower'] = null;
progress_intervals = new Array();
progress_intervals['#upper'] = null;
progress_intervals['#lower'] = null;
error_divs = new Array();
error_divs['#upper'] = d3.select("#derrorplot1");
error_divs['#lower'] = d3.select("#derrorplot2");
not_found_divs = new Array();
not_found_divs['#upper'] = d3.select("#d404plot1");
not_found_divs['#lower'] = d3.select("#d404plot2");
no_data_divs = new Array();
no_data_divs['#upper'] = d3.select("#demptyplot1");
no_data_divs['#lower'] = d3.select("#demptyplot2");


var content_pane = d3.select("#content")
var content_pane_toggle = d3.select("#sidebartoggle");
var right_pane = d3.select("#rightpane");
var content_pane_controls = d3.select("#contentoptions")
var tooltip = d3.select("body")
    .append("div")
    .style("position", "absolute")
    .style("z-index", "10")
    .style("visibility", "hidden")
    .style("background", "rgba(255,255,255,0.5)");


url = ""
sidebar_params = "";

function update_sidebar_inner() {
    if (url != "") {
	url = local_server + (content_diff_mode ? '/revdiff?' : '/revcontent?') + sidebar_params;
	var handler = function(text){return content_pane.html(text);};
	enqueue_req('content', url, handler);
    }
}
update_sidebar = run_with_delay(update_sidebar_inner);

gradient_log_mode = 1;
log_mode = 1;

reqs_db = {};

function enqueue_req(str_id, req, callback) {
    previous_request = reqs_db[str_id];
    reqs_db[str_id] = req;
    if ((previous_request === undefined)
	|| (previous_request === null)) {
	d3.text(req, function(text){
	    pending_request = reqs_db[str_id];
	    reqs_db[str_id] = null;
	    if (pending_request === req) {
		callback(text);
	    } else {
		enqueue_req(str_id, pending_request, callback);
	    }
	});
    }
}

function update_progress_info(plot_id) {
    d3.text(progress_urls[plot_id], function(text){progress_spans[plot_id].text(text);});
}


function enqueue_json_req(str_id, req, callback, item_id, progress_url) {
    if (servedreqs.indexOf(item_id) == -1) {
	servedreqs.push(item_id);
	wait_divs[str_id].style("visibility", "visible");
	progress_urls[str_id] = progress_url;
	var lambda = function () {update_progress_info(str_id);}
	clearInterval(progress_intervals[str_id]);
	progress_intervals[str_id] = setInterval(lambda, 5000);
    }
    previous_request = reqs_db[str_id];
    reqs_db[str_id] = {request: req, callback: callback, id: item_id, progress: progress_url};
    if ((previous_request === undefined)
	|| (previous_request === null)) {
	d3.json(req, function(text){
	    pending_request = reqs_db[str_id];
	    reqs_db[str_id] = null;
	    if (pending_request.request === req) {
		clearInterval(progress_intervals[str_id]);
		wait_divs[str_id].style("visibility", "hidden");		    
		progress_spans[str_id].text('');
		callback(text);
		pendingcount = 0;
		for (keys in reqs_db) { 
		    if ((reqs_db[keys] != null) 
			&& (reqs_db[keys] != undefined)) { 
			pendingcount++;
		    }
		}
	    } else {
		enqueue_json_req(str_id, pending_request.request, pending_request.callback, pending_request.id, pending_request.progress);
	    }
	});
    }
}

function append_plot(visualization, data, nplots, nrevs, plot_data_index, gradient_data_index, section_id, max_value, authors_list, max_value_section){
    /* Although this could/should be done through a more meaningful
     * attribute, detect the plot which should be upside-down through
     * its DOM id. */
    var current_scale;

    
    global_data[visualization.attr("id")][section_id] = data;

    if (visualization.attr("id") === "lower") {
	current_scale = vertical_scale_lower;
    } else {
	current_scale = vertical_scale_upper;
    }


    oury = function(d){return current_scale(d[12] + d[13]);};

    current_scale.domain([0, max_value]);

    oury0 = function(d){return current_scale(d[13]);};

    var url_prefix = 'host=' + visualization.attr('host')
	+ '&path=' + visualization.attr('path');
    if (visualization.attr('granularity') === 'section') {
	url_prefix += '&section=' + section_id;
    }

    var page_title = visualization.attr('title');


    var path = visualization
	.append("path")
	.data(data)
	.attr("class", "area");

    var stop_color_accessor;
	
    if (gradient_data_index === 4) {
	var author_scale = d3.scale.category20b().domain(authors_list);
	var highlighted_author_scale = d3.scale.category20c().domain(authors_list);
	var regular_stop_color_accessor = generate_author_color_acessor(authors_list, author_scale, 'gray');
	var highlighted_stop_color_accessor = generate_author_color_acessor(authors_list, highlighted_author_scale, 'black');
    } else {
	var regular_stop_color_accessor = gradient_stop_color_accessor(gradient_data_index, gradient_color_scale);
	var highlighted_stop_color_accessor = gradient_stop_color_accessor(gradient_data_index, gradient_highlight_color_scale);
    }


    var regular_gradient_id = generate_gradient(visualization, data, gradient_data_index, authors_list, section_id, regular_stop_color_accessor, max_value_section, gradient_color_scale);
    var h_gradient_id = generate_gradient(visualization, data, gradient_data_index, authors_list, '' + section_id +'h', highlighted_stop_color_accessor, max_value_section, gradient_highlight_color_scale);
    var gradient = d3.select('#' + regular_gradient_id);
    path
	.attr("fill", 'url(#' + regular_gradient_id + ')')
	.attr("class","plotarea")
	.attr("id",visualization.attr("id") + "plotarea" + section_id)
	.data(data)
	.attr("d", d3.svg.area()
              .x(x)
	      .y0(oury0)
	      .y1(oury)(data));

	path
	    .on('mouseover', 
		function(){
		    current_plot = gradient;
		    current_gradient_fill = gradient_data_index;
		    if(!tooltip_static) {
			path.attr("fill", 'url(#' + regular_gradient_id + 'h)')
			current_highlighted_plot = path;
			current_highlighted_gradient_fill = regular_gradient_id;
		    }})
	    .on('mouseout', function(){if(!tooltip_static) {
		path.attr("fill", 'url(#' + regular_gradient_id + ')')
		current_highlighted_plot = path;
		current_highlighted_gradient_fill = regular_gradient_id;}
				       else { current_plot = null;}})

    path
	.on('mousemove',function(d,i){
	    current = horizontal_scale.invert(d3.mouse(this)[0]);
	    if (!select_mode && !tooltip_static) {
		var i = binsearch(data, current);

		var next_ptr = i;
		i--;

		sidebar_params = url_prefix + '&revid=' + data[next_ptr][9];
		new_url = local_server + (content_diff_mode ? '/revdiff?' : '/revcontent?') + sidebar_params;
		if (new_url === url) {
		    return;
		}

		if (content_pane.style("visibility") === "visible") {
		    url = new_url;
		    update_sidebar();
		}
		if (data[next_ptr][11] === undefined) {
		    tooltip.html('<B>' + page_title + '</B><BR/>' 
				 + 'Section ' + section_id 
				 + ', revision <A TARGET="#blank" HREF="' + data[i][10] + '">' + data[i][9] + '</A> (section deleted) '
				 + '<TABLE CLASS="tooltiptable">'
				 + '<TR><TD>Author:</TD><TD>' + data[i][8] + '</TD></TR>'
				 + '<TR><TD>Quality:</TD><TD>' + data[i][0].toFixed(2) + '</TD></TR>'
				 + '<TR><TD>Vandalism:</TD><TD>' + data[i][1].toFixed(2) + '</TD></TR>'
				 + '<TR><TD>Controversy:</TD><TD>' + data[i][2].toFixed(2) + '</TD></TR>'
				 + '<TR><TD>Length:</TD><TD>' + data[i][3] + '</TD></TR>'
				 + '<TR><TD>Impact:</TD><TD>' + data[i][4].toFixed(2)  + '</TD></TR>'
				 + '<TR><TD>Timestamp: </TD><TD class="timestampcell">' + data[i][11] + '</TD></TR>'
				 + '<TR><TD>Deletion?</TD><TD>' + data[i][6]  + '</TD></TR>'
				 + '<TR><TD>Revert?</TD><TD>' + data[i][7] + '</TD></TR>'
				 + '</TABLE>');
		} else if (data[i][11] === undefined) {
		    tooltip.html('<B>' + page_title + '</B><BR/>' 
				 + 'Section ' + section_id 
				 + ', revision  <A  TARGET="#blank" HREF="' + data[next_ptr][10] + '">' + data[next_ptr][9] + '</A> (section created) '
				 + '<TABLE CLASS="tooltiptable">'
				 + '<TR><TD>Author:</TD><TD>' + data[next_ptr][8] + '</TD></TR>'
				 + '<TR><TD>Quality:</TD><TD>' + data[next_ptr][0].toFixed(2) + '</TD></TR>'
				 + '<TR><TD>Vandalism:</TD><TD>' + data[next_ptr][1].toFixed(2) + '</TD></TR>'
				 + '<TR><TD>Controversy:</TD><TD>' + data[next_ptr][2].toFixed(2) + '</TD></TR>'
				 + '<TR><TD>Length:</TD><TD>' + data[next_ptr][3] + '</TD></TR>'
				 + '<TR><TD>Impact:</TD><TD>' + data[next_ptr][4].toFixed(2)  + '</TD></TR>'
				 + '<TR><TD>Timestamp: </TD><TD class="timestampcell">' + data[next_ptr][11] + '</TD></TR>'
				 + '<TR><TD>Deletion?</TD><TD>' + data[next_ptr][6]  + '</TD></TR>'
				 + '<TR><TD>Revert?</TD><TD>' + data[next_ptr][7] + '</TD></TR>'
				 + '</TABLE>');
		} else {


		tooltip.html('<B>' + page_title + '</B><BR/>' 
			     + 'Section ' + section_id 
			     + ', revisions <A TARGET="#blank"  HREF="' + data[i][10] + '">' + data[i][9] + '</A> to '
			     + '<A TARGET="#blank"  HREF="' + data[next_ptr][10] + '">' + data[next_ptr][9] + '</A>'
			     + '<TABLE CLASS="tooltiptable">'
			     + '<TR><TD>Author:</TD><TD>' + data[i][8] + '</TD><TD>' + data[next_ptr][8] + '</TD></TR>'
			     + '<TR><TD>Quality:</TD><TD>' + data[i][0].toFixed(2) + '</TD><TD>' + data[next_ptr][0].toFixed(2) + '</TD></TR>'
			     + '<TR><TD>Vandalism:</TD><TD>' + data[i][1].toFixed(2) + '</TD><TD>' + data[next_ptr][1].toFixed(2) + '</TD></TR>'
			     + '<TR><TD>Controversy:</TD><TD>' + data[i][2].toFixed(2) + '</TD><TD>' + data[next_ptr][2].toFixed(2) + '</TD></TR>'
			     + '<TR><TD>Length:</TD><TD>' + data[i][3] + '</TD><TD>' + data[next_ptr][3] + '</TD></TR>'
			     + '<TR><TD>Impact:</TD><TD>' + data[i][4].toFixed(2)  + '</TD><TD>' + data[next_ptr][4].toFixed(2)  + '</TD></TR>'
			     + '<TR><TD>Timestamp: </TD><TD class="timestampcell">' + data[i][11] + '</TD><TD class="timestampcell">' + data[next_ptr][11] + '</TD></TR>'
			     + '<TR><TD>Deletion?</TD><TD>' + data[i][6]  + '</TD><TD>' + data[next_ptr][6]  + '</TD></TR>'
			     + '<TR><TD>Revert?</TD><TD>' + data[i][7] + '</TD><TD>' + data[next_ptr][7] + '</TD></TR>'
			     + '</TABLE>');
		}
	    }
	});
    
    visualization
	.append("path")
	.attr("class", "line")
	.attr("id",visualization.attr("id")+ "line" + section_id)
	.data(data)
	.attr("d", d3.svg.line()
	      .x(x)
	      .y(oury)(data));
}

function tooltip_disable(){
    if (!select_mode && !tooltip_static) {
	url = "";
	tooltip.style("visibility", "hidden");
    }
}

function tooltip_enable(){
    if (!select_mode) {
	tooltip.style("visibility", "visible");
    }
}

function tooltip_move(){
    if (!select_mode && !tooltip_static) {
	tooltip
	    .style("top", (d3.event.pageY)+"px")
	    .style("left",(d3.event.pageX+10)+"px");
    }
}

function tooltip_toggle_freeze(){
    if (!select_mode) {
	if (d3.event != undefined) {
	    d3.event.stopPropagation();
	}
	if (tooltip_static && (current_highlighted_plot != null) && (current_highlighted_plot != current_plot)) {
	    current_highlighted_plot.attr("fill", 'url(#' + current_highlighted_gradient_fill + ')');
	    if (current_plot != null) {
		current_plot.attr("fill", 'url(#' + current_gradient_fill + 'h)');
	    }
	    current_highlighted_plot = current_plot;
	    current_highlighted_gradient_fill = current_gradient_fill;
	}
	tooltip_static = !tooltip_static;
    }
}

undo_store = new Array();

function zoom_out() {
    console.log('zoom out');
    last_setting = undo_store.pop();
    if (last_setting != undefined){
	replot_range(last_setting[0], last_setting[1]);
    } else {
	var current_half_window = (end_date - start_date) / 2;
	zoom(current_half_window * 0.5);
    }
}

function zoom_in() {
    console.log('zoom in');
    var current_half_window = (end_date - start_date) / 2;
    zoom(-current_half_window * 0.5);
}

function clear_plot(plot_id) {
    global_data['upper'] = new Array();
    global_data['lower'] = new Array();


    vertical_scale_lower.domain([0, 0 ]);
    vertical_scale_upper.domain([0, 0 ]);
    

    d3.select('#container_minor').attr("transform","translate(0,0)")
    not_found_divs[plot_id].style("visibility", "hidden");
    no_data_divs[plot_id].style("visibility", "hidden");
    error_divs[plot_id].style("visibility", "hidden");
    $(plot_id).empty();
}

function plot_data(page_title,plot_id, plot_data_index_string, gradient_data_index_string, count_str, granularity, start, end, host, path) {
    clear_plot(plot_id);

    var query_url = local_server + '/reqpage?'
	+ 'title=' + page_title 
	+ '&granularity=' + granularity 
	+ '&start=' + start 
	+ '&end=' + end
	+ '&host=' + host
	+ '&path=' + path;

    var progress_url = local_server + '/progress?'
	+ 'page=' + page_title 
	+ '&host=' + host
	+ '&path=' + path;

    enqueue_json_req(plot_id, query_url, function(json) {
	if (json == null) {
	    /* Known errors and special cases always return something
	     * other than null and are handled after this block. Null
	     * means serious trouble. */
	    console.log('[' + host + path + '] Something bad happened. No data at all.');
	    error_divs[plot_id].style("visibility", "visible");
	    return;
	}
	if (json == "404") {
	    not_found_divs[plot_id].style("visibility", "visible");
	    console.log('[' + host + path + '] There is no such article: ' + page_title);
	    return;
	}
	if ((json.length == 0) || (json[0].length == 0) || (json[0][0].length == 0)) {
	    no_data_divs[plot_id].style("visibility", "visible");
	    console.log('[' + host + path + '] There is no data to plot on plot ' + plot_id + ' concerning ' + page_title);
	    return;
	} 

	var article_level = json[0][0];
	var talk_page = json[2];
	json = json[1];
	
	num_plots = json.length;
	num_revs = json[0].length;
	vis = d3.select(plot_id);
	plot_data_index = parseInt(plot_data_index_string);
	gradient_data_index = parseInt(gradient_data_index_string);

	/* Store some plot parameters, for use when replotting. */
	vis.attr('title', page_title);
	vis.attr('granularity', granularity);
	vis.attr('host', host);
	vis.attr('path', path);

	/* Preprocess incoming data: accumulate timeline data into the
	 * first section and define timestamps for all entries. */
	authors = new Array();
	var ts;
	for (i = 0; i < num_revs; i++) {
	    for (j = 0; j < num_plots; j++) {
		if (json[j][i] != null) {
		    if (authors[json[j][i][8]] === undefined) {
			authors[json[j][i][8]] = 1;
		    } else {
			authors[json[j][i][8]] += 1;
		    }
		    json[j][i][11] = date_to_string(new Date(json[j][i][5] * 1000));
		}
	    }
	}

	// Javascript does not seem to have a built-in way to do this
	// (sort list of key: value by value), so this list must be
	// first converted into an array of tuples, which can be
	// sorted using regular array sorting functions (just by
	// crafting an appropriate lambda function). The desired
	// result can be then easily extracted. See (and inspired by)
	// http://wolfram.kriesing.de/blog/index.php/2008/javascript-sort-object-by-a-value
	authors_list_tmp = new Array();
	for (var author in authors) {
	    authors_list_tmp.push([author, authors[author]]);
	}
	authors_list_tmp.sort(function(a, b) {return b[1] - a[1]})
	authors_list = authors_list_tmp.map(function (a) {return a[0];}).slice(0,20);

	/* Prepare incoming data to be rendered as a stack of plots
	 * (compute and define y0, which gets used later) */
	var y_accessor = function (d) {return d[12];};


	var max_value = 0;
	var start_section = new Array(num_plots);
	var end_section = new Array(num_plots);
	for (i = 0; i < num_plots; i++) {
	    start_section[i] = -1;
	    end_section[i] = 0;
	}
	var revision = 0;
	var tmp;
	for (revision = 0; revision < num_revs; revision++) {
	    var len = 0;
	    var section;
	    for (section = 0; section < num_plots; section++) {
		if (json[section][revision] != null) {
		    tmp = json[section][revision][plot_data_index];
		    len += tmp;
		    end_section[section] = revision;
		} else {
		    if (start_section[section] == revision - 1) {
			start_section[section] = revision;	
		    }	    
		}
	    }
	    if (len > max_value) {
		max_value = len;
	    }
	}

	if (log_mode) {
	    max_value = Math.log(999 * max_value + 1)/6.908;
	}

	var min_plot_height_1 = (max_value / num_plots) * 0.05;
	var min_plot_height_2 = (max_value / vis_height) * 10;
	var min_plot_height = Math.max (min_plot_height_1, min_plot_height_2);

	/* Now (sigh) we have to 1) increase small values and 2)
	 * recompute the maximum sum. */
	var max_value = 0;
	var revision = 0;
	var tmp;
	var tmp_grad;
	var max_vals_section = new Array(num_plots);
	var max_gradient_vals_section = new Array(num_plots);
	for (i = 0; i < num_plots; i++) {
	    max_vals_section[i] = 0;
	    max_gradient_vals_section[i] = 0;
	}
	for (revision = 0; revision < num_revs; revision++) {
	    var len = 0;
	    var section;
	    var wasnull;
	    for (section = 0; section < num_plots; section++) {
		wasnull = 0;
		if ((start_section[section] <= revision)
		    && (end_section[section] >= (revision - 1))) {
		    if (json[section][revision] === null) {
			rebuilt++;
			wasnull = 1;
			json[section][revision] = new Array(12);
			json[section][revision][0] = 0;
			json[section][revision][1] = 0;
			json[section][revision][2] = 0;
			json[section][revision][3] = 0;
			json[section][revision][4] = 0;
			json[section][revision][5] = article_level[revision][5];
			json[section][revision][6] = 0;
			json[section][revision][7] = 0;
			json[section][revision][8] = '';
			json[section][revision][9] = article_level[revision][9];
			json[section][revision][10] = '';
			json[section][revision][11] = article_level[revision][11];
			json[section][revision][12] = 0;
		    }
		    /* Get y0 for this revision+section (previous one, if it exists) */
		    var prev_section = section;

		    /* Look for previous plots. */
		    while ((--prev_section  >= 0)
			   && (json[prev_section][revision] == null)) {
			;
		    }
		    /* If there is no plot, prev_section == -1*/
		    if (prev_section == -1) {
			json[section][revision][13] = 0;
		    } else {
			/* Otherwise, it is the closest section */
			json[section][revision][13] = 
			    json[prev_section][revision][12]
			    + json[prev_section][revision][13];
		    }

		    tmp = json[section][revision][plot_data_index];
		    if (log_mode) {
			tmp = Math.log(999 * tmp + 1)/6.908;
		    }
		    if ((tmp < min_plot_height) && (wasnull == 0)) {
			tmp = min_plot_height;
		    }
		    json[section][revision][12] = tmp;
		    len += tmp;
		    if (tmp > max_vals_section[section]) {
			max_vals_section[section] = tmp;
		    }
		    tmp_grad = json[section][revision][gradient_data_index];
		    if (log_mode) {
			tmp_grad = Math.log(999 * tmp + 1)/6.908;
		    }
		    if (tmp_grad > max_gradient_vals_section[section]) {
			max_gradient_vals_section[section] = tmp_grad;
		    }
		    if (section < (num_plots - 1)) {
			/* update y0 */
			var next_section = section;
			while ((++next_section < num_plots) 
			       && (json[next_section][revision] == null)) {
			    ;
			}
			if (next_section < num_plots) {
			    json[next_section][revision][13] = json[section][revision][13] + tmp;
			}
		    }
		}
	    }
	    if (len > max_value) {
		max_value = len;
	    }
	}

	/* For each section, plot it */
	var tmp;
	for (var i in json) {	    
	    tmp = json[i].slice((start_section[i] > -1)?start_section[i]:0, ((end_section[i] + 1) < num_revs)?(end_section[i] + 2):(end_section[i] + 1));
	    if ((max_vals_section[i] > 0) && (tmp.length > 0)) {
	    	append_plot(vis, tmp, num_plots, num_revs, plot_data_index, gradient_data_index, i, max_value, authors_list, max_gradient_vals_section[i]);
	    }
	}

	/* Add the timeline/axis */
	xline = d3.time.scale().domain([start_date * 1000, end_date * 1000]).range([0, vis_width]);
	xAxis = d3.svg.axis().scale(xline).tickSubdivide(true).ticks(8);

	if (plot_id == "#upper") {
	    xAxis.orient("bottom");
	} else {
	    xAxis.orient("top");
	}

	axis = vis.append("svg:g")
	    .attr("class", "x axis");

	if (plot_id == "#upper") {
	    axis.attr("transform", "translate(0," + plot_height + ")");
	}	    
	axis.call(xAxis);


	timeline_x = function(d) { return x(d) - 1.5;};
	timeline_y = function(d) { return - 5 * (1 + d[4]);};
	timeline_height = function(d) { return 10 * (1 + d[4]);};
	timeline_marker_fill = function(d) { 
	    if (d[7] == 1) {
		return "blue";
	    } else if (d[6] == 1) { 
		return "red"; 
	    } else { 
		return "white";
	    }
	};

	minimum = horizontal_scale.invert(2.5) - horizontal_scale.invert(1);

	var len = article_level.length;
	var i = 0, j = 0;
	var newts = new Array();
	newts.push(article_level[0]);
	for (i = 1; i < len; i++) {
	    if (((article_level[i][5] - newts[j][5]) < minimum) 
		&& (j >= 0))  {
		newts[j][6] |= article_level[i][6];
		newts[j][7] |= article_level[i][7];
		newts[j][4] = Math.max(article_level[i][4], newts[j][4]);
	    } else {		
		newts.push(article_level[i]);
		j++;
	    }
	}
	article_level = null;

	axis.selectAll("rect")
	    .data(newts)
	    .enter().append("svg:rect")
	    .attr("x", timeline_x)
	    .attr("y", timeline_y)
	    .attr("fill", timeline_marker_fill)
	    .attr("width", 3)
	    .attr("height", timeline_height);

	var len = talk_page.length;
	var i = 0, j = 0;
	var newts = new Array();
	newts.push(talk_page[0]);
	for (i = 1; i < len; i++) {
	    if ((talk_page[i] - newts[j]) >= minimum) {
		newts.push(talk_page[i]);
		j++;
	    }
	}

	talk_page = null;

	axis.selectAll("circle")
	    .data(newts)
	    .enter().append("svg:circle")
	    .attr("cx", horizontal_scale)
	    .attr("r", "3px");



	/* Cleanup: We have lots of closures pointing to this. */
	json = null;
	if (duo_normalization_mode == true) {
	    changeit();
	}
    }, host + path + page_title, progress_url);
}

function pan_mousedown_handler(){ 
    var y = d3.mouse(this)[1];
    if ((y > (plot_height + 10))
	&& (y < (plot_height + 60))) { 
	if (d3.event.button== 0){
	    d3.event.stopPropagation();
	    tooltip_disable();
	    timeline_drag_start = d3.mouse(this)[0];
	}
    }
}

function pan_mousemove_handler(){
    var y = d3.mouse(this)[1];
    if ((y > (plot_height + 10))
	&& (y < (plot_height + 60))) { 
	d3.event.stopPropagation();
	if (cursor == 0) {
	    d3.select("#plots_svg_container").style("cursor", "ew-resize");
	    cursor = 1;
	}
	if (timeline_drag_start != null) {
	    var x = d3.mouse(this)[0];
	    var delta = x - timeline_drag_start;
	    d3.select('#container_minor').attr("transform","translate(" + delta + ",0)")
	}
    } else {
	if (cursor == 1) {
	    d3.select("#plots_svg_container").style("cursor", "default");
	    cursor = 0;
	    if (timeline_drag_start != null) {
		d3.event.stopPropagation();
		timeline_drag_stop = d3.mouse(this)[0];
		var range_start = horizontal_scale.invert(timeline_drag_start);
		timeline_drag_start = null;
		var range_end = horizontal_scale.invert(timeline_drag_stop);
		var shift = range_end - range_start;
		replot_range(start_date - shift, end_date - shift);
	    }
	}
    }
}

/* Handle mouse clicks and moves at the "top" level (implements the
 * selection-and-zoom). */
d3.select("#plots_svg_container")
    .on('mousedown', pan_mousedown_handler)
    .on('mousemove', pan_mousemove_handler)
    .on('mouseup', end_drag_plot)
    .on('mouseover', pan_mouseover_handler);

function pan_mouseover_handler(){
    var y = d3.mouse(this)[1];
    if ((y > (plot_height + 10))
	&& (y < (plot_height + 60))) { 
	if (timeline_drag_start != null) {
	    d3.event.stopPropagation();
	    timeline_drag_stop = d3.mouse(this)[0];
	    var range_start = horizontal_scale.invert(timeline_drag_start);
	    timeline_drag_start = null;
	    var range_end = horizontal_scale.invert(timeline_drag_stop);
	    var shift = range_end - range_start;
	    replot_range(start_date - shift, end_date - shift);
	}
	
    }
}

function end_drag_plot(){ 
    var y = d3.mouse(this)[1];
    if ((y > (plot_height + 10))
	&& (y < (plot_height + 60))) { 
	if (timeline_drag_start != null) {
	    d3.event.stopPropagation();
	    timeline_drag_stop = d3.mouse(this)[0];
	    var range_start = horizontal_scale.invert(timeline_drag_start);
	    timeline_drag_start = null;
	    var range_end = horizontal_scale.invert(timeline_drag_stop);
	    var shift = range_end - range_start;
	    replot_range(start_date - shift, end_date - shift);
	}
    }
}

// Keep track of the panning cursor mode
cursor = 0;

// tooltip inspired on https://gist.github.com/1016860
d3.selectAll(".plotarea")
    .on('contextmenu', function (d, i) { 
	d3.event.stopPropagation();

	// Avoid context menu
	d3.event.preventDefault();
	if (select_mode == 0) {
	    zoom_out();
	} else {
	    select_mode = 0;
	    destroy_selection();
	}
    })
    .on("mouseover", tooltip_enable)
    .on("mouseout", tooltip_disable)
    .on('click', tooltip_toggle_freeze)
    .on('mousemove', function (d, i) {  
	tooltip_move();
	if (select_mode == 1) {
	    update_selection(d3.mouse(this)[0],
			     d3.mouse(this)[1]); 
	}
    })
    .on('mouseup', function (d, i) {
	if ((d3.event.button == 0) && (select_mode == 1)) {
	    select_mode = 0;
	    destroy_selection();
	    if (click_start_px != click_end_px) {
		focus_on_range("#upper", click_start_px, click_end_px);
	    } else {
		tooltip.style("visibility", "visible");
	    }
	}
    })
    .on('mousedown', function (d, i){
	mx = d3.mouse(this)[0];
	my = d3.mouse(this)[1];
	if (d3.event.button == 0) {
	    select_mode = 1;
	    tooltip.style("visibility", "hidden");
	    create_selection(mx, my);
	}
    });

function pad_to_two(num) {
    return ((num >= 10) ? '' : '0') + num;
}

function date_to_string(date){
    return ''
	+ date.getFullYear() + '/'
	+ pad_to_two(date.getMonth() + 1) + '/'
	+ pad_to_two(date.getDate()) + ' '
	+ pad_to_two(date.getHours()) + ':'
	+ pad_to_two(date.getMinutes()) + ':'
	+ pad_to_two(date.getSeconds());    
}

function update_date_divs(from_date, to_date) {
    document.getElementById("date_form").from_date.value = date_to_string(from_date);
    document.getElementById("date_form").to_date.value = date_to_string(to_date);
}

function update_plot_width() {
    update_width();
    update_plots();
}

function toggle_content() {
    if (content_pane.style("visibility") === "visible") {
	content_pane_width = $("#content").width();
	content_pane_controls.style("display", "none");
	content_pane.html("");
	content_pane.style("width", "0px");
	content_pane.style("overflow", "hidden");
	content_pane.style("visibility", "hidden");
	content_pane.style("padding", "0px");
	content_pane_toggle.text("«");	
	$("#rightpane").resizable( "disable" );	    
	right_pane.style("left", "");
	right_pane.style("width", "");
	update_plot_width();
    } else {
	$("#rightpane").resizable( "enable" );
	content_pane_controls.style("display", "block");
	content_pane.html("&nbsp;");
	content_pane.style("visibility", "visible");
	content_pane.style("padding", "10px");
	content_pane.style("overflow", "scroll"); /* http://reference.sitepoint.com/css/overflow */
	$("#content").width(content_pane_width);
	content_pane_toggle.text("»");
	update_plot_width();
    }
}

replotting = 0;
replot_after = 0;

function update_dates() {
    var date_form = document.getElementById("date_form");
    var new_from_date = Date.parse(date_form.from_date.value);
    if (isNaN(new_from_date)) {
	date_form.from_date.style['background'] = '#ff7777';
    } else {
	date_form.from_date.style['background'] = '';
	start_date = new_from_date / 1000;
    }

    var new_to_date = Date.parse(date_form.to_date.value);
    if (isNaN(new_to_date)) {
	date_form.to_date.style['background'] = '#ff7777';
    } else {
	date_form.to_date.style['background'] = '';
	end_date = new_to_date / 1000;
    }    
}

function ajax_autocomplete(plot_id) {
    var form = document.getElementById(plot_id);

    if (autocomplete_mode == 0) {
	form.article.style['background'] = 'none';
	return;
    }


    site = sites[form.site.selectedIndex];

    $.getJSON("http://" + site[0] + site[1] + 'api.php?action=opensearch&search=' + form.article.value +"&callback=?",
	      null,
	      function(a,b,c){if (a[1].length == 0) {form.article.style['background'] = '#ff7777';} else {form.article.style['background'] = 'none';}
			     $("input#" + plot_id + "article").autocomplete({
				 source: a[1],
				 select: update_plots
});});
}

update_plots = run_with_delay(update_plots_inner);

function update_plots_inner() {
    if (replotting != 0) {
	replot_after = 1;
	return;
    }
    console.log('updating plot');

    if (select_mode == 1) {
	select_mode = 0;
	destroy_selection();
    }

    if (tooltip_static == 1) {
	tooltip_toggle_freeze();
    }


    replotting = 1;

    update_dates();
    horizontal_scale.domain([start_date, end_date])

    opt_form = document.getElementById("general_options");
    log_mode = opt_form.log.checked;
    gradient_log_mode = opt_form.gradient_log.checked;

    if (gradient_log_mode == 1) {
	gradient_stop_color_accessor = gradient_stop_color_accessor_log;
    } else {
	gradient_stop_color_accessor = gradient_stop_color_accessor_normal;
    }

    var plot1 = document.getElementById('plot1');
    var plot2 = document.getElementById('plot2');

    var plot1_title = plot1.article.value;
    var plot2_title = plot2.article.value;

    var plot1_granularity = plot1.granularity.options[plot1.granularity.selectedIndex].value;
    var plot2_granularity = plot2.granularity.options[plot2.granularity.selectedIndex].value;

    mirror_mode =  ((plot1_title === plot2_title) 
		    && (plot1_granularity === plot2_granularity));

    ids = ['plot1', 'plot2'];
    for (var i in ids) {
	var f = document.getElementById(ids[i]);
	var site = sites[f.site.selectedIndex];
	var article = f.article.value;

	plot_data(article,
		  f.plotid.value,
		  f.plot.selectedIndex,
		  f.gradient.selectedIndex,
		  '',
		  f.granularity.options[f.granularity.selectedIndex].value,
		  start_date,
		  end_date,
		  site[0],
		  site[1]);
    }
    replotting = 0;
    if (replot_after == 1) {
	replot_after = 0;
	console.log('update_plots();');
    }
}

function zoom(margin) {
    var new_start = start_date - margin;
    var new_end = end_date + margin;
    if (new_end > new_start) {
	update_date_divs(new Date(new_start * 1000), 
			 new Date(new_end * 1000));
	update_plots();
    }
}


var timelineKey = d3.select("#talk_rev_key").
  append("svg:svg").
  attr("width", 10).
  attr("height", 10);

timelineKey.append("circle").attr("cx",5).attr("cy",5).attr("r", 3);

var timelineKey = d3.select("#regular_rev_key").
  append("svg:svg").
  attr("width", 10).
  attr("height", 15);

timelineKey.append("rect").attr("x", 2).attr("y", 0).attr("stroke", "black").attr("width",3).attr("height", 15).attr("fill","white");

var timelineKey = d3.select("#revert_rev_key").
  append("svg:svg").
  attr("width", 10).
  attr("height", 15);

timelineKey.append("rect").attr("x", 2).attr("y", 0).attr("stroke", "black").attr("width",3).attr("height", 15).attr("fill","blue");


var timelineKey = d3.select("#del_rev_key").
  append("svg:svg").
  attr("width", 10).
  attr("height", 15);

timelineKey.append("rect").attr("x", 2).attr("y", 0).attr("stroke", "black").attr("width",3).attr("height", 15).attr("fill","red");



var timelineKey = d3.select("#timeline_impact_key").
  append("svg:svg").
  attr("width", 150).
  attr("height", 20);

timelineKey.append("rect").attr("x", 40).attr("y", 0).attr("stroke", "black").attr("width",3).attr("height", 20).attr("fill","white");
timelineKey.append("rect").attr("x", 50).attr("y", 2.5).attr("stroke", "black").attr("width",3).attr("height", 15).attr("fill","white");
timelineKey.append("rect").attr("x", 60).attr("y", 5).attr("stroke", "black").attr("width",3).attr("height", 10).attr("fill","white");

timelineKey.append('svg:text').text('greater')
    .attr('x', '2')
    .attr('y', '15')
    .attr('text-anchor', 'left')
    .attr('style', 'font-size: 10;')
    .attr('fill', 'black');

timelineKey.append('svg:text').text('smaller')
    .attr('x', '70')
    .attr('y', '15')
    .attr('text-anchor', 'left')
    .attr('style', 'font-size: 10;')
    .attr('fill', 'black');

var rectDemo = d3.select("#key").
  append("svg:svg").
  attr("width", 80).
  attr("height", 20);

gradient = rectDemo.append("svg:defs")
    .append("svg:linearGradient")
    .attr("id", 'keygradient')
    .attr("x1", "0%")
    .attr("y1", "0%")
    .attr("x2", "100%")
    .attr("y2", "0%")
    .attr("spreadMethod", "pad");

gradient.append('svg:stop').attr('offset', '0%').attr('stop-color', 'orange');
gradient.append('svg:stop').attr('offset', '100%').attr('stop-color', 'blue');

rectDemo.append('svg:rect').
  attr('x', 0).
  attr('y', 0).
  attr('height', 20).
  attr('width', 80).
  attr('fill', 'url(#keygradient)');

rectDemo.append('svg:text').text('0')
    .attr('x', '2')
    .attr('y', '15')
    .attr('text-anchor', 'left')
    .attr('style', 'font-size: 12; font-weight:bold ')
    .attr('fill', 'black');

rectDemo.append('svg:text').text('1')
    .attr('x', '68')
    .attr('y', '15')
    .attr('text-anchor', 'left')
    .attr('style', 'font-size: 12; font-weight:bold ')
    .attr('fill', 'white');

var rectDemo = d3.select("#keyb").
  append("svg:svg").
  attr("width", 80).
  attr("height", 20);

gradientb = rectDemo.append("svg:defs")
    .append("svg:linearGradient")
    .attr("id", 'keygradientb')
    .attr("x1", "0%")
    .attr("y1", "0%")
    .attr("x2", "100%")
    .attr("y2", "0%")
    .attr("spreadMethod", "pad");

gradientb.append('svg:stop').attr('offset', '0%').attr('stop-color', 'yellow');
gradientb.append('svg:stop').attr('offset', '100%').attr('stop-color', 'darkgreen');

rectDemo.append('svg:rect').
  attr('x', 0).
  attr('y', 0).
  attr('height', 20).
  attr('width', 80).
  attr('fill', 'url(#keygradientb)');

rectDemo.append('svg:text').text('0')
    .attr('x', '2')
    .attr('y', '15')
    .attr('text-anchor', 'left')
    .attr('style', 'font-size: 12; font-weight:bold ')
    .attr('fill', 'black');

rectDemo.append('svg:text').text('1')
    .attr('x', '68')
    .attr('y', '15')
    .attr('text-anchor', 'left')
    .attr('style', 'font-size: 12; font-weight:bold ')
    .attr('fill', 'white');


function fill_select(select,options,lone_value) {
    if (options.length > 0) {
	select.options.length = options.length;
	for (i in options) {
	    select.options[i] = new Option(options[i]);
	}
	select.selectedIndex = 0;
	select.style['background'] = 'none';
    } else {
	// No matches
	select.style['background'] = '#ff7777';
	select.options.length = 1;
	select.selectedIndex = 0;
    }
    select.options[select.options.length] = new Option(lone_value);
}

function install_wiki(host, path, name) {
    sites.push([host, path]);
    ids = ['plot1', 'plot2'];
    for (i in ids) {
	var form = document.getElementById(ids[i]);
	var wiki_select = form.site;
	wiki_select.options[wiki_select.options.length] = new Option(name);
	if (wiki_select.options.length === 1) {
	    wiki_select.selectedIndex = 0;
	}
    }

}

content_diff_mode = 0;

function change_content_mode(){
    var form = document.getElementById('contentoptionsform');
    content_diff_mode = form.diff.checked ? 1 : 0;
    update_sidebar();
}

// Install wikipedias.
install_wiki("en.wikipedia.org","/w/","English");
install_wiki("de.wikipedia.org","/w/","Deutsch");
install_wiki("fr.wikipedia.org","/w/","Français");
install_wiki("nl.wikipedia.org","/w/","Nederlands");
install_wiki("it.wikipedia.org","/w/","Italiano");
install_wiki("pl.wikipedia.org","/w/","Polski");
install_wiki("es.wikipedia.org","/w/","Español");
install_wiki("ru.wikipedia.org","/w/","Русский");
install_wiki("ja.wikipedia.org","/w/","日本語");
install_wiki("pt.wikipedia.org","/w/","Português");
install_wiki("zh.wikipedia.org","/w/","中文");
install_wiki("sv.wikipedia.org","/w/","Svenska");
install_wiki("vi.wikipedia.org","/w/","Tiếng Việt");
install_wiki("uk.wikipedia.org","/w/","Українська");
install_wiki("ca.wikipedia.org","/w/","Català");
install_wiki("no.wikipedia.org","/w/","Norsk (Bokmål)");
install_wiki("fi.wikipedia.org","/w/","Suomi");
install_wiki("cs.wikipedia.org","/w/","Čeština");
install_wiki("hu.wikipedia.org","/w/","Magyar");
install_wiki("ko.wikipedia.org","/w/","한국어");
install_wiki("fa.wikipedia.org","/w/","فارسی");
install_wiki("id.wikipedia.org","/w/","Bahasa Indonesia");
install_wiki("tr.wikipedia.org","/w/","Türkçe");
install_wiki("ro.wikipedia.org","/w/","Română");
install_wiki("ar.wikipedia.org","/w/","العربية");
install_wiki("sk.wikipedia.org","/w/","Slovenčina");
install_wiki("eo.wikipedia.org","/w/","Esperanto");
install_wiki("da.wikipedia.org","/w/","Dansk");
install_wiki("sr.wikipedia.org","/w/","Српски / Srpski");
install_wiki("lt.wikipedia.org","/w/","Lietuvių");
install_wiki("eu.wikipedia.org","/w/","Euskara");
install_wiki("ms.wikipedia.org","/w/","Bahasa Melayu");
install_wiki("sl.wikipedia.org","/w/","Slovenščina");
install_wiki("he.wikipedia.org","/w/","עברית");
install_wiki("bg.wikipedia.org","/w/","Български");
install_wiki("kk.wikipedia.org","/w/","Қазақша");
install_wiki("vo.wikipedia.org","/w/","Volapük");
install_wiki("war.wikipedia.org","/w/","Winaray");
install_wiki("hr.wikipedia.org","/w/","Hrvatski");
install_wiki("hi.wikipedia.org","/w/","हिन्दी");
install_wiki("en.wikibooks.org" ,"/w/", "WikiBooks");

$("#rightpane").resizable({handles: 'w', alsoResize: "#content", stop: update_plot_width});
$("#rightpane").resizable( "disable" );


// Add an invisible rectangle over the timeline. Helps providing a
// better panning experience.
d3.select('#container_minor').append('svg:rect').attr('id','invisible_rect').attr('height',50).attr('width', vis_width).attr("transform", "translate(0," + plot_height + ")").attr('fill','white').attr('opacity',0);

d3.select('#container_minor_rect').attr('height', '100%').attr('width', vis_width).attr('fill','white').style('stroke','white')
    .on('mouseover', pan_mouseover_handler)
    .on('contextmenu', function (d, i) { 
	var y = d3.mouse(this)[1];
	if ((y <= (plot_height + 10))
	    || (y >= (plot_height + 60))) { 

	    // Avoid context menu
	    d3.event.preventDefault();
	    if (select_mode == 0) {
		zoom_out();
	    } else {
		select_mode = 0;
		destroy_selection();
	    }
	}
    })
    .on('mousemove', function (d, i) {
	var y = d3.mouse(this)[1];
	if ((y <= (plot_height + 10))
	    || (y >= (plot_height + 60))) { 
	    if (select_mode == 1) {
		update_selection(d3.mouse(this)[0],
				 d3.mouse(this)[1]); 
	    }
	}
    })	
    .on('mouseup', function (d, i) {
	if ((d3.event.button == 0) && (select_mode == 1)) {
	    select_mode = 0;
	    destroy_selection();
	    if (click_start_px != click_end_px) {
		focus_on_range("#upper", click_start_px, click_end_px);
	    } else {
		tooltip.style("visibility", "visible");
	    }
	}
    })
    .on('mousedown', function (d, i){
	var y = d3.mouse(this)[1];
	if ((y <= (plot_height + 10))
	    || (y >= (plot_height + 60))) { 
	    mx = d3.mouse(this)[0];
	    my = d3.mouse(this)[1];
	    if (d3.event.button == 0) {
		select_mode = 1;
		tooltip.style("visibility", "hidden");
		create_selection(mx, my);
	    }
	}
    });



function changeit () {
    var num_plots_upper = global_data['upper'].length;
    var num_plots_lower = global_data['lower'].length;
    var max_val = Math.max(vertical_scale_lower.domain()[1], vertical_scale_upper.domain()[1]);

    vertical_scale_lower.domain([0, max_val ]);
    vertical_scale_upper.domain([0, max_val ]);

    var current_scale;


    for (var plot in global_data) {
	var oury;
	var oury0;
	oury = function(d){return current_scale(d[12] + d[13]);};
	oury0 = function(d){return current_scale(d[13]);};  

	if (plot === "lower") {
	    current_scale = vertical_scale_lower;
	} else {
	    current_scale = vertical_scale_upper;
	}


	for (var i in global_data[plot]) {
	    
	    d3.select('#' + plot + "plotarea"+i)
		.attr("d", d3.svg.area()
		      .x(x)
		      .y0(oury0)
		      .y1(oury)(global_data[plot][i]));
	    d3.select('#' + plot + "line"+i)
		.attr("d", d3.svg.line()
		      .x(x)
		      .y(oury)(global_data[plot][i]));
	}

    }
}

function toggle_duo_norm(){
    duo_normalization_mode = !duo_normalization_mode;
}


// This line should always be the very last one in the file, as it
// renders the plots for the initial, default values.
update_plots();
